/*
* Copyright (c) 2009, Active Web Solutions Limited
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1- Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2- Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3- The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
*
* THIS SOFTWARE IS PROVIDED BY Active Web Solutions Limited ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Active Web Solutions Limited BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.aws.jazure.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;

import net.aws.jazure.TableStorageEntity;
import net.aws.jazure.TableStorageProperty;
import net.aws.jazure.httpconstants.NameSpaces;
import net.aws.jazure.httpconstants.XMLElements;

import org.jdom.Attribute;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.jdom.output.DOMOutputter;
import org.jdom.output.XMLOutputter;
import org.w3c.dom.Document;

/**
 * This singleton class provides methods for constructing and parsing ATOM feed XML documents
 * using JDOM.
 * 
 * @author dyaa.albakour
 *
 */
public class ATOMHelper {
	
	
	// ATOM elements should be stored as constants.
	
	/**
	 * Creates a DOM document representing the ATOM feed required to create a table
	 * 
	 * @param tableName
	 * @return
	 * @throws IOException
	 * @throws ParserConfigurationException 
	 * @throws JDOMException 
	 */
	public static Document createATOMFeedDOMDocument(String tableName) throws IOException, ParserConfigurationException, JDOMException{
		
		org.jdom.Document jdomdoc = getCreateTableATOMFeedJDOMDocument(tableName);
		
		// for debugging, please remove
//		XMLOutputter outputter = new XMLOutputter();
//		outputter.output(jdomdoc, System.out);
		
		// now convert to a DOM document
		DOMOutputter domoutputter = new DOMOutputter();
		Document document  = domoutputter.output(jdomdoc);
		
		return document;
		
	}
	
	/**
	 * 
	 * Creates a String ATOM feed required to create a new Table.
	 * 
	 * @param tableName the name of the table
	 * @return the ATOM feed as String
	 * @throws IOException if an error occured during writing the feed. 
	 */
	public static String getCreateTableATOMFeed(String tableName) throws IOException{
		org.jdom.Document jdomdoc=  getCreateTableATOMFeedJDOMDocument(tableName);
		
		XMLOutputter xmlOutputter = new XMLOutputter();
		StringWriter strwriter = new StringWriter();
		xmlOutputter.output(jdomdoc, strwriter);
		
		return strwriter.toString();		
	}
	
	public static org.jdom.Document getCreateTableATOMFeedJDOMDocument(String tableName){
		
		
		// initialize name spaces
		Namespace metdataNS = 
			Namespace.getNamespace(NameSpaces.MetaData_Prefix,NameSpaces.MetaData);
		
		Namespace dataServiceNS =
			Namespace.getNamespace(NameSpaces.DataServices_Prefix, NameSpaces.DataServices);
		
		Namespace atomNS = 	Namespace.getNamespace(NameSpaces.ATOMFeed);
		
		Element entry = new Element("entry",atomNS);
		entry.addNamespaceDeclaration(metdataNS);
		entry.addNamespaceDeclaration(dataServiceNS);
		
		//title
		Element titleElement = new Element("title",atomNS);
		entry.addContent(titleElement);
		
		//updated
		Element updatedElement = new Element("updated",atomNS);
		
		SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
		String formattedCurrentDateTime =  fmt.format(Calendar.getInstance().getTime());
		
		updatedElement.addContent(formattedCurrentDateTime);
		entry.addContent(updatedElement);
		
		//author
		Element authorElement = new Element("author",atomNS);
		authorElement.addContent(new Element("name",atomNS));
		entry.addContent(authorElement);
		
		//id
		Element idElement = new Element("id",atomNS);
		entry.addContent(idElement);
		
		//content element
		Element contentElement = new Element("content",atomNS);
		contentElement.setAttribute(new Attribute("type","application/xml"));
		
		Element propertiesElement = new Element("properties",metdataNS);
		
		Element tableNameElement = new Element("TableName",dataServiceNS);
		tableNameElement.addContent(tableName);
		propertiesElement.addContent(tableNameElement);
		
		contentElement.addContent(propertiesElement);
		
		entry.addContent(contentElement);
		
		// create the JDOM document
		org.jdom.Document jdomdoc = new org.jdom.Document(entry);
		
		return jdomdoc;
	}
	
	public static org.jdom.Document getInsertEntityATOMFeedJDOMDocument(TableStorageEntity entity)
		throws UnsupportedEncodingException{
		// initialize name spaces
		Namespace metdataNS = 
			Namespace.getNamespace(NameSpaces.MetaData_Prefix,NameSpaces.MetaData);
		
		Namespace dataServiceNS =
			Namespace.getNamespace(NameSpaces.DataServices_Prefix, NameSpaces.DataServices);
		
		Namespace atomNS = 	Namespace.getNamespace(NameSpaces.ATOMFeed);
		
		Element propertiesElement = new Element(XMLElements.properties,metdataNS);
		
		Element entry = new Element("entry",atomNS);
		entry.addNamespaceDeclaration(metdataNS);
		entry.addNamespaceDeclaration(dataServiceNS);
		
		//title
		Element titleElement = new Element("title",atomNS);
		entry.addContent(titleElement);
		
		//updated
		Element updatedElement = new Element("updated",atomNS);
		
		SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
		String formattedCurrentDateTime =  fmt.format(Calendar.getInstance().getTime());
		
		updatedElement.addContent(formattedCurrentDateTime);
		entry.addContent(updatedElement);
		
		//author
		Element authorElement = new Element("author",atomNS);
		authorElement.addContent(new Element("name",atomNS));
		entry.addContent(authorElement);
		
		//id
		Element idElement = new Element("id",atomNS);
		entry.addContent(idElement);
		
		//content element
		Element contentElement = new Element("content",atomNS);
		contentElement.setAttribute(new Attribute("type","application/xml"));
		
		
		// add the partition key, row key and time stamp elements
		// ** After changing the TableStorageEntity class (removing the String and Calendar variables...
		// ** we don't need to do so anymore
		
		/*
		Element partitionKeyElement = new Element(XMLElements.PartitionKey,dataServiceNS);
		partitionKeyElement.addContent(entity.getPartitionKey());
		propertiesElement.addContent(partitionKeyElement);
		
		Element rowKeyElement = new Element(XMLElements.RowKey,dataServiceNS);
		rowKeyElement.addContent(entity.getRowKey());
		propertiesElement.addContent(rowKeyElement);
		*/
		
		// When I first tried this I was passing the current timestamp in the
		// timestamp element.
		// After removing this, I got it working..
//		Element timestampElement = new Element (XMLElements.Timestamp,dataServiceNS);		
//		timestampElement.setAttribute(new Attribute(XMLElements.type,
//													TableStorageProperty.EdmDateTime,
//													metdataNS));	
//		String formattedTimestamp =  fmt.format(entity.getTimeStamp().getTime());
//		timestampElement.addContent(formattedTimestamp);	
		
//		propertiesElement.addContent(timestampElement);
		
		// Now add the rest of the properties.
		// ** Now add all the properties in one lump 		
		for(String propertyName:entity.getProperties().keySet()){
			if(propertyName.equals(TableStorageProperty.Timestamp))
				continue;
			TableStorageProperty property = entity.getProperties().get(propertyName);
			Element element = new Element(propertyName,dataServiceNS);
			if(!property.getType().equals(TableStorageProperty.EdmString))
				element.setAttribute(new Attribute(XMLElements.type,property.getType(),metdataNS));
			element.addContent(property.marshallValue());			
			propertiesElement.addContent(element);
		}
		
		contentElement.addContent(propertiesElement);
		
		entry.addContent(contentElement);
		
		// create the JDOM document
		org.jdom.Document jdomdoc = new org.jdom.Document(entry);
		
		return jdomdoc;
	}
	
	/**
	 * 
	 * Creates a String ATOM feed required to insert an entity into a table.
	 * @param entity the entity
	 * @return the ATOM feed as String.
	 * @throws IOException if an error occurred while writing the feed.
	 */
	public static String getInsertEntityATOMFeed(TableStorageEntity entity) 
		throws IOException{
		
		org.jdom.Document jdomdoc =  getInsertEntityATOMFeedJDOMDocument(entity);
		
		XMLOutputter xmlOutputter = new XMLOutputter();
		StringWriter strwriter = new StringWriter();
		xmlOutputter.output(jdomdoc, strwriter);
		
		return strwriter.toString();
	}
	
	public static org.jdom.Document getUpdateEntityATOMFeedJDOMDocument(
			TableStorageEntity entity, String entityurl) 
	throws UnsupportedEncodingException{
		
		// initialize name spaces
		Namespace metdataNS = 
			Namespace.getNamespace(NameSpaces.MetaData_Prefix,NameSpaces.MetaData);
		
		Namespace dataServiceNS =
			Namespace.getNamespace(NameSpaces.DataServices_Prefix, NameSpaces.DataServices);
		
		Namespace atomNS = 	Namespace.getNamespace(NameSpaces.ATOMFeed);
		
		Element propertiesElement = new Element(XMLElements.properties,metdataNS);
		
		Element entry = new Element("entry",atomNS);
		entry.addNamespaceDeclaration(metdataNS);
		entry.addNamespaceDeclaration(dataServiceNS);
		
		//title
		Element titleElement = new Element("title",atomNS);
		entry.addContent(titleElement);
		
		//updated
		Element updatedElement = new Element("updated",atomNS);
		
		SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
		String formattedCurrentDateTime =  fmt.format(Calendar.getInstance().getTime());
		
		updatedElement.addContent(formattedCurrentDateTime);
		entry.addContent(updatedElement);
		
		//author
		Element authorElement = new Element("author",atomNS);
		authorElement.addContent(new Element("name",atomNS));
		entry.addContent(authorElement);
		
		//id
		Element idElement = new Element("id",atomNS);
		idElement.addContent(entityurl);
		entry.addContent(idElement);
		
		//content element
		Element contentElement = new Element("content",atomNS);
		contentElement.setAttribute(new Attribute("type","application/xml"));
		
		
		// add the partition key, row key and time stamp elements
		/*
		Element partitionKeyElement = new Element(XMLElements.PartitionKey,dataServiceNS);
		partitionKeyElement.addContent(entity.getPartitionKey());
		propertiesElement.addContent(partitionKeyElement);
		
		Element rowKeyElement = new Element(XMLElements.RowKey,dataServiceNS);
		rowKeyElement.addContent(entity.getRowKey());
		propertiesElement.addContent(rowKeyElement);
		
		

		Element timestampElement = new Element (XMLElements.Timestamp,dataServiceNS);		
		timestampElement.setAttribute(new Attribute(XMLElements.type,
													TableStorageProperty.EdmDateTime,
													metdataNS));	
		String formattedTimestamp =  fmt.format(entity.getTimeStamp().getTime());
		timestampElement.addContent(formattedTimestamp);		
		propertiesElement.addContent(timestampElement);
		*/
		
		// Now add the rest of the properties.
		for(String propertyName:entity.getProperties().keySet()){
			TableStorageProperty property = entity.getProperties().get(propertyName);
			Element element = new Element(propertyName,dataServiceNS);
			if(!property.getType().equals(TableStorageProperty.EdmString))
				element.setAttribute(new Attribute(XMLElements.type,property.getType(),metdataNS));
			element.addContent(property.marshallValue());
			
			propertiesElement.addContent(element);
		}
		
		contentElement.addContent(propertiesElement);
		
		entry.addContent(contentElement);
		
		// create the JDOM document
		org.jdom.Document jdomdoc = new org.jdom.Document(entry);
		
		return jdomdoc;
	}
	
	
	/**
	 * 
	 * Creates a String ATOM feed required to update an entity in a table.
	 * @param entity the entity
	 * @return the ATOM feed as String.
	 * @throws IOException if an error occurred while writing the feed.
	 */
	public static String getUpdateEntityATOMFeed(TableStorageEntity entity, String entityurl) throws IOException{
		org.jdom.Document jdomdoc =  getUpdateEntityATOMFeedJDOMDocument(entity,entityurl);
		
		XMLOutputter xmlOutputter = new XMLOutputter();
		StringWriter strwriter = new StringWriter();
		xmlOutputter.output(jdomdoc, strwriter);
		
		return strwriter.toString();
	}
	
	/**
	 * Parse a table storage entity from an ATOM feed.
	 * 
	 * @param xmlFeedStream An InputStream containing the ATOM feed.
	 * @return a TableStorageEntity object.
	 * @throws Exception if parsing the Feed failed. 
	 */
	public static TableStorageEntity getEntityFromATOMFeed(InputStream xmlFeedStream) 
		throws Exception
		{
		
		Namespace metdataNS = 
			Namespace.getNamespace(NameSpaces.MetaData_Prefix,NameSpaces.MetaData);
		
		Namespace dataServiceNS =
			Namespace.getNamespace(NameSpaces.DataServices_Prefix, NameSpaces.DataServices);
		
		Namespace atomNS = 	Namespace.getNamespace(NameSpaces.ATOMFeed);
		
		SAXBuilder saxBuilder =  new SAXBuilder();
		
		org.jdom.Document document = saxBuilder.build(xmlFeedStream);
		
		Element propertiesElement =
			document.getRootElement().getChild(XMLElements.content,atomNS).
			getChild(XMLElements.properties,metdataNS);
		
		// create a new TableStorageEntity
		TableStorageEntity entity = new TableStorageEntity();
		
		List nodeList = propertiesElement.getChildren();
		
		Iterator it= nodeList.iterator();
		
		while(it.hasNext()){
			Element element = (Element)it.next();
			TableStorageProperty property = new TableStorageProperty();
			
			String type = element.getAttributeValue(XMLElements.type,metdataNS);
				
			property.unmrshallValue(type , element.getText() );
			entity.getProperties().put(element.getName(),property);	
		}
		
		/*
		 * Xpath didn't work, it was complaining about a missing dependency!
		 * 
		XPath xpath = XPath.newInstance("/entry/content/properties");
		
		List list = xpath.selectNodes(document);
		
		Iterator it =list.iterator();
		
		while(it.hasNext()){
			
			Element element = (Element)it.next();
			
			System.out.println(element.getText());
			
		}*/
		
		return entity;
	}
	
	/**
	 * Parse the given ATOM feed into a list of entities.
	 * 
	 * @param xmlFeedStream an InputStream representing the ATOM feed.
	 * @return A list of TableStorageEntity objects.
	 * @throws Exception if the parsing fails.
	 */
	public static List<TableStorageEntity> getEntitiesFromATOMFeed(InputStream xmlFeedStream) 
		throws Exception{
		
		// TODO 
		Namespace metdataNS = 
			Namespace.getNamespace(NameSpaces.MetaData_Prefix,NameSpaces.MetaData);
		
		Namespace dataServiceNS =
			Namespace.getNamespace(NameSpaces.DataServices_Prefix, NameSpaces.DataServices);
		
		Namespace atomNS = 	Namespace.getNamespace(NameSpaces.ATOMFeed);
		
		SAXBuilder saxBuilder =  new SAXBuilder();
		
		org.jdom.Document document = saxBuilder.build(xmlFeedStream);
		
		Element feedElement =document.getRootElement();
		
		List entries = feedElement.getChildren(XMLElements.entry, atomNS);
		
		Iterator entryIt = entries.iterator();
		
		ArrayList<TableStorageEntity> entitiesList = new ArrayList<TableStorageEntity>();
		
		while(entryIt.hasNext()){
			
			Element entryElement= (Element)entryIt.next();
			
			Element propertiesElement =
				entryElement.getChild(XMLElements.content,atomNS).
				getChild(XMLElements.properties,metdataNS);
			
			// create a new TableStorageEntity
			TableStorageEntity entity = new TableStorageEntity();
			
			List nodeList = propertiesElement.getChildren();
			
			Iterator it= nodeList.iterator();
			
			while(it.hasNext()){
				Element element = (Element)it.next();
				TableStorageProperty property = new TableStorageProperty();
				
				String type = element.getAttributeValue(XMLElements.type,metdataNS);
					
				property.unmrshallValue(type , element.getText() );
				entity.getProperties().put(element.getName(),property);	
			}
			
			entitiesList.add(entity);
		}
		
		
		return entitiesList;
		
	}
	
	public static void main(String[] args) throws Exception{
		createATOMFeedDOMDocument("myTable");
		
	}
	
	

}
